﻿using System;
using System.Collections.Generic;
using System.Text;

using System.IO;

using NAudio;
using NAudio.Wave;
using System.Threading;

namespace LiBLaB.Audio
{
    public class MP3 : IDisposable
    {

        enum StreamingPlaybackState
        {
            Stopped,
            Playing,
            Buffering,
            Paused
        }

        IWavePlayer waveOutDevice;
        Mp3FileReader mp3;
        bool playing = false;
        private IMp3FrameDecompressor decompressor = null;
        private BufferedWaveProvider bufferedWaveProvider = null;
        private volatile StreamingPlaybackState playbackState;
        List<byte[]> frameBuffer = new List<byte[]>();
        int bufferCounter = 0;

        public byte[] NextFrame 
        { 
            get {
                if (mp3 != null && mp3.Position < mp3.Length)
                    return mp3.ReadNextFrame().RawData;
                return new byte[0]{};
                }
        }

        public MP3()
        {
        }

        public MP3(string FileName)
        {
            if (!File.Exists(FileName))
                throw new Exception("MP3 file deosn't exist.");
            mp3 = new Mp3FileReader(FileName);
            
            waveOutDevice = new WaveOut();
            waveOutDevice.Init(mp3);
        }

        ~MP3()
        {
            Dispose();
        }

        public void Play()
        {
            if (!playing && mp3 != null)
            {
                playing = true;
                waveOutDevice.Play();
            }
        }

        private void StreamMP3(object state)
        {
            while (playbackState != StreamingPlaybackState.Stopped)
            {
                if ((bufferCounter >= frameBuffer.Count) ||
                    (bufferedWaveProvider != null &&
                    bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes < bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4))
                {
                    Thread.Sleep(500);
                }
                else
                {
                    MemoryStream ms = new MemoryStream(frameBuffer[bufferCounter++]);
                    Mp3Frame frame = Mp3Frame.LoadFromStream(ms);
                    if (decompressor == null)
                    {
                        WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
                        decompressor = new AcmMp3FrameDecompressor(waveFormat);
                        bufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
                        this.bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(20); // allow us to get well ahead of ourselves
                        waveOutDevice = new WaveOut();
                        waveOutDevice.Init(bufferedWaveProvider);
                    }
                    byte[] decompressed_buffer = new byte[16384 * 4];
                    int decompressed = decompressor.DecompressFrame(frame, decompressed_buffer, 0);
                    bufferedWaveProvider.AddSamples(decompressed_buffer, 0, decompressed);
                }

            }
        }

        public void Play(byte[] Frame)
        {
            frameBuffer.Add(Frame);
            if (!playing)
            {
                //Run AddSamples thread
                playing = true;
                playbackState = StreamingPlaybackState.Playing;
                ThreadPool.QueueUserWorkItem(new WaitCallback(StreamMP3));
                ThreadPool.QueueUserWorkItem(new WaitCallback(PlayStream));
                //Run Play thread
            }
        }

        private void PlayStream(object state)
        {
            while (playbackState != StreamingPlaybackState.Stopped)
            {
                if (waveOutDevice != null && bufferedWaveProvider != null)
                {
                    var bufferedSeconds = bufferedWaveProvider.BufferedDuration.TotalSeconds;
                    // make it stutter less if we buffer up a decent amount before playing
                    if (bufferedSeconds < 0.5 && this.playbackState == StreamingPlaybackState.Playing)
                    {
                        waveOutDevice.Pause();
                        this.playbackState = StreamingPlaybackState.Buffering;
                    }
                    else if(bufferedSeconds > 4)
                    {
                        waveOutDevice.Play();
                        this.playbackState = StreamingPlaybackState.Playing;
                    }
                }
                Thread.Sleep(500);
            }
        }

        public void Seek(int Percent)
        {
            if (mp3 != null)
            {
                long offset = mp3.Length * Percent / 100;
                mp3.Seek(offset, SeekOrigin.Begin);
            }
        }

        public void Pause()
        {
            if (playing && waveOutDevice != null)
            {
                playing = false;
                waveOutDevice.Pause();
            }
        }

        public void Stop()
        {
            playbackState = StreamingPlaybackState.Stopped;
        }

        public void Dispose()
        {
            Dispose(true);
            // Use SupressFinalize in case a subclass 
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (mp3 != null)
                {
                    Stop();
                    mp3.Close();
                }
                if (waveOutDevice != null)
                {
                    waveOutDevice.Stop();
                    //waveOutDevice.Dispose();
                }
                if (bufferedWaveProvider != null)
                    bufferedWaveProvider.ClearBuffer();
                if (decompressor != null)
                    decompressor.Dispose();
            }
            mp3 = null;
            waveOutDevice = null;
            bufferedWaveProvider = null;
            decompressor = null;
        }
    }
}